#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:Venus
@file:第一版.py
@time:2021/12/04/21：25
"""
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
import time
import pylab as mpl

# ----------------------------导入数据----------------------------
# np.random.seed(1)
# 读取数据集
# winequality = pd.read_csv(r'C:\Users\Venus\Desktop\编程学习\数据汇总集\红酒数据集\winequality-red.csv', ';')
winequality = pd.read_csv(r'C:\Users\Venus\Desktop\Pycharm_Workspace\venv\智能优化方法\大作业\data\winequality-red1500-2.csv', ';')
# 数据和标签拆开
# 250
# wine_data = winequality.iloc[1:252, :-1]
# wine_target = winequality.iloc[1:252, -1]
# 1500
wine_data = winequality.iloc[:, :-1]
wine_target = winequality['quality']


# !!-----------所有特征都用的时候  score=0.755--测试集准确度为0.64---------
# winequality_data_train, winequality_data_test, winequality_taget_train, winequality_taget_test = train_test_split(
#     wine_data, wine_target, test_size=0.2, random_state=6)
# stdScale = StandardScaler().fit(winequality_data_train)
# winequality_trainScaler = stdScale.transform(winequality_data_train)
# # winequality_testScaler = stdScale.transform(winequality_data_test)
# clf = KNeighborsClassifier()
# clf.fit(winequality_trainScaler, winequality_taget_train)
# print("11:",clf.score(winequality_trainScaler, winequality_taget_train))

# ---------------------------------------------------------------


def cross(old_chromosome, parent_chromosome, pop_num):
    # 策略1. 交叉染色体片段
    if np.random.rand() < cross_rate:
        n = np.random.randint(0, pop_num, size=1)
        cut_point1 = int(np.random.randint(0, chrom_length, size=1))
        cut_point2 = int(np.random.randint(0, chrom_length, size=1))
        if cut_point1 < cut_point2:
            old_chromosome[cut_point2 - cut_point1:chrom_length - cut_point1] = parent_chromosome[n, cut_point2:]
        else:
            old_chromosome[cut_point1 - cut_point2:chrom_length - cut_point2] = parent_chromosome[n, cut_point1:]
    return old_chromosome
    # ------------------------------------------------------------------------------------------------------------
    # 策略2. 随机交叉三位基因点
    # if np.random.rand() < cross_rate:
    #     for i in range(3):
    #         cut_point = int(np.random.randint(0, chrom_length, size=1))
    #         cross_chromosome = best_chromosome[-1]
    #         if cross_chromosome[cut_point] == 1:
    #             old_chromosome[cut_point] = cross_chromosome[cut_point]
    # return old_chromosome
    # ------------------------------------------------------------------------------------------------------------
    # 策略3. 随机交叉三位基因点
    # if np.random.rand() < cross_rate:
    #     for i in range(5):
    #         n = np.random.randint(0, pop_num, size=1)
    #         cut_point = int(np.random.randint(0, chrom_length, size=1))
    #         old_chromosome[cut_point] = parent_chromosome[n, cut_point]
    # return old_chromosome


def mutation(child_i, chrom_length):
    # -----------------1.变异策略---------------------
    for i in range(chrom_length):  # 遍历染色体的每一位
        if np.random.rand() < mutation_rate:  # 对每一个位置比较是否小于变异率
            if child_i[i] == 0:
                child_i[i] = 1
            if child_i[i] == 1:
                child_i[i] = 0
    return child_i


def choose(parent_chromosome, fitness, pop_num):
    old_chromosome = parent_chromosome.copy()
    idx = np.random.choice(np.arange(pop_num), size=pop_num, replace=True, p=fitness / sum(fitness))
    return old_chromosome[idx]


def fitness_single(child_i):
    chrom_i = child_i.copy()
    chrom_i_data = wine_data.copy()  # <class 'numpy.ndarray'>
    chrom_i_target = wine_target.copy()

    # 判断是否全为0，全为0代表所有特征都不选，明显不符合题意了
    sum_flag = 0
    for i in chrom_i:  # 遍历这条染色体上的每一位基因
        sum_flag = sum_flag + i
    if sum_flag == 0:
        chrom_i = np.random.randint(0, 2, chrom_length - 1)  # 重新随机生成一下
        chrom_i = np.append(chrom_i, 1)  # 防止又随机生成全0列表
        chrom_i = chrom_i.tolist()

        # print(chrom_i)
    # 判断需要剔除哪些特征
    for i in range(chrom_length):
        if chrom_i[i] == 0:
            chrom_i_data.pop(name[i])
    # print("chrom_i:", chrom_i)
    # print("chrom_i_data:", chrom_i_data)

    # 划分训练集和测试集
    winequality_data_train, winequality_data_test, winequality_taget_train, winequality_taget_test = train_test_split(
        chrom_i_data, chrom_i_target, test_size=0.2, random_state=6)
    # 标准化数据集
    stdScale = StandardScaler().fit(winequality_data_train)
    winequality_trainScaler = stdScale.transform(winequality_data_train)
    winequality_testScaler = stdScale.transform(winequality_data_test)

    # 训练KNN
    clf = KNeighborsClassifier(n_neighbors=18)
    clf.fit(winequality_trainScaler, winequality_taget_train)
    # --------------fitness-------------------
    # 1.把精确度作为fitness
    train_score.append(clf.score(winequality_trainScaler, winequality_taget_train))
    test_score.append(clf.score(winequality_testScaler, winequality_taget_test))
    # 2.把正确率作为fitness
    prediction = clf.predict(winequality_testScaler)
    correct_num = 0
    for i in range(len(winequality_taget_test)):
        if prediction[i] == winequality_taget_test.values[i]:
            correct_num += 1
    correct_rate = correct_num / len(winequality_taget_test)
    obj = correct_rate
    # -----------------------------------------
    return obj


def fitness_multiple(parent_chromosome, pop_num, chrom_length):
    fitness_mul = np.array([])
    train_score_mul = []
    test_score_mul = []
    parent_chromosome_list = parent_chromosome.copy()
    for i in parent_chromosome_list:  # 遍历每一条染色体
        # -----------------------------------------------
        parent_chromosome_list_i_rate= fitness_single(i)
        fitness_mul = np.append(fitness_mul, parent_chromosome_list_i_rate)

    train_accuracy.append(max(train_score))
    test_accuracy.append(max(test_score))

    train_score.clear()
    test_score.clear()

    return fitness_mul


# 参数定义
pop_num = 20  # 种群数量
itera_max = 30  # 迭代次数

chrom_length = 11  # 染色体长度
cross_rate = 0.85  # 交叉概率
mutation_rate = 0.05  # 变异概率

# 特征名称
name = ["fixed acidity", "volatile acidity", "citric acid", "residual sugar",
        "chlorides", "free sulfur dioxide", "total sulfur dioxide", "density",
        "pH", "sulphates", "alcohol"]
# 参数记录
fitness = []
best_chromosome = []
best_chromosome_score = []
history_chromosome = []
history_chromosome_score = []

train_accuracy = []
test_accuracy = []

train_score=[]
test_score=[]
# --------------设置汉字格式-----------
# 常见的无衬线字体有 Trebuchet MS, Tahoma, Verdana, Arial, Helvetica,SimHei 中文的幼圆、隶书等等
mpl.rcParams['font.sans-serif'] = ['FangSong']  # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题

if __name__ == '__main__':
    # 开始时间
    start_time = time.time()
    # ---------------------------------------------1.初始化-----------------------------------------
    chromosome_list = np.random.randint(0, 2, (pop_num, chrom_length))

    init_chromosome_list_copy = chromosome_list.copy()
    # 判断是否全为0，全为0代表所有特征都不选，明显不符合题意了
    sum_flag = 0
    for i in init_chromosome_list_copy:  # 遍历所有染色体
        for j in i:  # 遍历每条染色体上的每一位基因
            sum_flag = sum_flag + j
        if sum_flag == 0:
            i = np.random.randint(0, 2, chrom_length - 1)  # 重新随机生成一下
            i = np.append(i, 1)  # 防止又随机生成全0列表

    # -------------------把所有特征都用上的时候的正确率当做初始适应度---------------------------
    # 划分数据集
    winequality_data_train, winequality_data_test, winequality_taget_train, winequality_taget_test = train_test_split(
        wine_data, wine_target, test_size=0.2, random_state=6)
    # 标准化数据集
    stdScale = StandardScaler().fit(winequality_data_train)
    winequality_trainScaler = stdScale.transform(winequality_data_train)
    winequality_testScaler = stdScale.transform(winequality_data_test)
    # 训练KNN
    clf = KNeighborsClassifier(n_neighbors=18)
    clf.fit(winequality_trainScaler, winequality_taget_train)

    train_accuracy.append(clf.score(winequality_trainScaler, winequality_taget_train))
    test_accuracy.append(clf.score(winequality_testScaler, winequality_taget_test))

    # --------------fitness-------------------
    prediction = clf.predict(winequality_testScaler)
    correct_num = 0
    for k in range(len(winequality_taget_test)):
        if prediction[k] == winequality_taget_test.values[k]:
            correct_num += 1
    correct_rate = correct_num / len(winequality_taget_test)
    fitness.append(correct_rate)
    # -------------------------------------------

    # 初始最优精度
    init_chromosome = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    best_chromosome.append(init_chromosome)
    best_chromosome_score.append(max(fitness))
    print("初始最优染色体", best_chromosome)
    print("初始最优准确率", best_chromosome_score)
    history_chromosome.append(init_chromosome)
    history_chromosome_score.append(max(fitness))

    # ---------------------------------------------2.迭代-----------------------------------------
    best_itera = 0
    for i in range(itera_max):
        print("第", i + 1, "次：")
        # 1.计算适应值
        parent_chromosome = chromosome_list.copy()  #
        fitness = fitness_multiple(parent_chromosome, pop_num, chrom_length)  # <class 'numpy.ndarray'>
        # 2.选择
        parent_chromosome = choose(parent_chromosome, fitness, pop_num)
        # 3.交叉
        for old_chromosome in parent_chromosome:

            child_i = cross(old_chromosome, parent_chromosome, pop_num)
            # 4.变异
            child_i = mutation(child_i, chrom_length)
            # 5.计算单条染色体的适应度
            child_i_fitness = fitness_single(child_i)

            if child_i_fitness > np.mean(fitness):  # 平均值

                parent_chromosome[np.argmin(fitness), :] = child_i
                fitness[np.argmin(fitness)] = child_i_fitness

        # 6.更新父代
        print("本次最佳值:", max(fitness))
        chromosome_list = parent_chromosome.copy()
        # 7.记录最优解
        history_chromosome.append(chromosome_list[np.argmax(fitness)].copy())
        history_chromosome_score.append(max(fitness))

        if max(fitness) > best_chromosome_score[-1]:
            best_chromosome.append(chromosome_list[np.argmax(fitness)].copy())
            best_chromosome_score.append(max(fitness))
            print("发现一个更优解---->,染色体为：", best_chromosome[-1], "，函数值为：", best_chromosome_score[-1])
            best_itera = i + 1
        else:
            best_chromosome_score.append(best_chromosome_score[-1])
            best_chromosome.append(best_chromosome[best_chromosome_score.index(best_chromosome_score[-1])])

        # print_value = fitness_multiple(parent_chromosome, pop_num, chrom_length)
        # max_value_idx = np.argmax(print_value)
        # print("最优染色体为：", select_list[max_value_idx])
        # print("最优适应值为：", print_value[max_value_idx])
        fitness = np.array([])

    # -------------------------打印结果--------------------------------------
    end_time = time.time()  # 结束时间
    print("--------------实验结果---------------")
    print("实验用时： ", end_time - start_time, "秒")
    # 最优解
    print("最后得到的最优解为第", best_itera, "次", best_chromosome[-1])
    print("         最优精确度为：", best_chromosome_score[-1])

    # 画出最优解迭代
    best_Solution_Itera = []
    for i in range(len(best_chromosome)):
        best_Solution_Itera.append(i)

    plt.plot(best_Solution_Itera, best_chromosome_score, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.title('最优值历史')
    plt.xlabel('迭代次数')
    plt.ylabel('最优值')
    plt.show()

    # 画出解迭代
    history_chromosome_Solution_Itera = []
    for i in range(len(history_chromosome_score)):
        history_chromosome_Solution_Itera.append(i)

    plt.plot(history_chromosome_Solution_Itera, history_chromosome_score, 'r-', color='#4169E1', alpha=0.8,
             linewidth=0.8)
    plt.title('适应值历史')
    plt.xlabel('迭代次数')
    plt.ylabel('适应值')
    plt.show()

    itera_max_settings = range(1, itera_max+2)
    plt.plot(itera_max_settings, train_accuracy, label="training accuracy")
    plt.plot(itera_max_settings, test_accuracy, label="test accuracy")
    plt.xlabel("itera_max")
    plt.ylabel("score")
    plt.legend()
    plt.show()
